HTML5 × CSS3 × jQueryを真面目に勉強 – #8 jQueryプラグインの作り方について詳しく
僕は人の名前を覚えるのが苦手です。それはさておき、jQueryプラグインの作成方法について頻繁に忘れるので、手順をここにまとめておくことにします。コレさえ読めば急にプラグインを大量に作れといった無茶ぶりをされても大丈夫。
多い日も安心♪(ゝω・)vキャピ
はじめに - jQuery プラグインの構成
細かい差はあれど、基本的にjQueryプラグインは以下のような構成で成り立っています。
// 匿名関数で全体をラップ - (5) (function($) { // このプラグインの名前 - (1) $.fn.name_space = function() { //要素を退避 - (2) var elements = this; // 要素をひとつずつ処理 - (3) elements.each(function() { // 具体的な処理をここに記述 }); // method chain用に要素を返す - (4) return this; }; }) (jQuery);
- $.fnオブジェクトをプラグイン名(※カスタムjQueryメソッド)で拡張する
- thisには指定された要素が格納されており、これを変数に退避しておく
- 指定された要素全てに処理が適用されるように、each()を使って一つずつに同様の処理を行う
- method chainが途切れないように、最後にreturn thisを書く
- 全体を匿名関数でラップする
ではサンプルを作りながら一つ一つを見ていくとします。
jQueryプラグインの第一歩
まずは最小規模なものからはじめます。
jquery.myplugin.js
// このプラグインの名前 $.fn.myplugin = function() { //要素を退避 var elements = this; // 要素をひとつずつ処理 elements.each(function() { $('body').append('<div>Welcome to the ' + this.innerHTML + ' world!</div>'); }); // method chain用に要素を返す return this; };
jQueryプラグインは、カスタムjQueryメソッドとしてjQueryを拡張したものになります。定義の仕方は、$.fnオブジェクトをプラグインの名前(カスタムメソッド名)で拡張します。
thisには指定された要素が格納されています。これを変数に退避しておきます。※必須というわけではありません。していないプラグインも沢山あります。
jQueryオブジェクトは要素が複数あることが前提となっています。そのためelementsにも複数の要素が含まれていると考え、それらに対して全て同様に処理しなくてはなりません。ということでeach()を使ってelementsをループに掛けて、要素を一つずつ処理してきます。
jQueryにはMethod chainという、処理を連鎖させるというアーキテクチャに基づいています。
Method chainの例
$('div') // div要素を取得 .hide() // 取得したdiv要素を隠す .text('new context') // 隠したdiv要素にテキストを'new context'にする .addClass('update') // クラスを追加する .show(); // div要素を表示する
このような書き方が成立するのは、hide()やtext()といったメソッドが処理の最後に対象の要素を丸ごと返しているからです。jQueryプラグインでカスタムメソッドを定義する際も、最後に要素を返すことで、処理を連鎖(chain)させることが出来ます。返す方法はカスタムメソッド内の最後にreturn this;を書くだけです。決まり文句として覚えておきましょう。
これで新しいjQueryプラグインが出来ました。使い方はjQueryデフォルトの関数とまったく同じです。以下のように新しいメソッド名を指定すれば実行されます。
Method chainの例
$('div').myplugin();
jQueyプラグインにオプションを渡す
カスタムメソッドにオプションを渡すことで、プラグインの利便性を何倍にも高めることが出来ます。オプションの渡す方法は、optionsオブジェクトを通じてカスタムメソッドに渡すのが一般的です。optionsオブジェクトにすることで、複数のパラメータをまとめて渡すようにします。
プラグインにオプション機能を追加する際は、デフォルト値を定義するのがセオリーです。そのデフォルト値に対してプラグインのユーザーは任意のオプションを渡して(※全てではない)上書きできるようにします。
jquery.shake.js
$.fn.shake = function(options) { // 要素を退避 var elements = this; // 渡されたオプションとデフォルトをマージする var opts = $.extend({}, $.fn.shake.defaults, options); // 要素をひとつずつ処理 elements.each(function() { for (var i=0; i<opts.shakes; i++) { $(this).animate({marginLeft: opts.x}, opts.speed) .animate({marginLeft: opts.x * -1}, opts.speed); } // 要素を元に戻す $(this).animate({marginLeft: 0}, opts.speed); }); // method chain用に要素を返す return this; }; // shakeプラグインのデフォルトオプション $.fn.shake.defaults = { speed: 'slow', shakes: 2, x: 10 }; [/javascript] <p>デフォルトオプションは上記のようにして定義します。これを<span class="bold">extend()</span>メソッドをつかって、ユーザーが指定したオプションで上書きします。extend()の第一引数に <span class="bold">{}</span> <span class="ash">(空オブジェクト)</span>を指定すると、デフォルトオプションそのものは上書きされずに、デフォルトオプションとユーザー指定オプションがマージされたものが作られます。</p> <p>デフォルトオプションを定義することで、プラグインのユーザーは必要なオプションのみを指定することが出来ます。</p> <p>このように記述すると、$ショートカットはプラグイン内でのみ有効となり、プラグインの外側ではそちら側の都合で$ショートカットを自由に使うことができるので、互いに競合することがなくなります。また、匿名関数の冒頭に<span class="bold">セミコロン</span>をつけると、<span class="red">「大変だ!このプラグインの前に読み込まれたプラグインの末尾にセミコロンが抜けていたせいでJSエラーが起きましたッ!!」</span>といった恥ずかしいミスを防ぐことが出来ます。</p> <p>もう一つのメリットとして、プラグインを匿名関数でラップすると<span class="bold">クロージャ</span>が作成されることになります。これによりプラグイン内の変数や関数が適切な名前空間において定義され、他のコードと競合することを防ぐことが出来ます。</p> <h2 id="toc-jquery2">jQueryプラグインに内部用(プライベート)関数を追加する</h2> <p class="code-summary bold">jquery.shake2.js</p> <p>一つ前のサンプルで、プラグインは匿名関数でラップされました。これにより匿名関数内では、プライベート関数を普通に定義することが出来ます。当然、外部から直接この関数を呼ぶことは出来ません。パブリックメソッドとプライベートメソッドを分離する事で、プラグインのソースコードを整理することが出来、より精度の高いものに成長させる事が出来ます。</p> <ul> <li><a href="http://wakamsha.github.com/dev.cm/jhc-study/08/shake2.html" target="_blank" rel="noopener noreferrer">View demo (shake2)</a></li> </ul> <h2 id="toc-">独自データ属性をサポートする</h2> <p><span class="bold">HTML5</span>には、HTMLの名前空間に属さない<span class="bold">ユーザーオリジナルの属性</span>を定義することが出来る機能が備わっています。これを<span class="bold cybercyan">独自データ属性</span>と呼びます。よく知らない、より詳しく知りたいという方は、<a href="http://www.html5.jp/tag/attributes/data.html" target="_blank" rel="noopener noreferrer">こちらのページ</a>をご参照ください。</p> <p>独自データ属性をサポートすると、プラグインのユーザーがオプションを渡す為の方法が一つ増える事になります。また、オプションをHTML要素に持たせる事で、JavaScriptのコードが複雑化するのを軽減する事が出来ます。</p> <p class="code-summary bold">jquery.shake3.js</p> <p class="code-summary bold">HTML</p> <ul> <li><a href="http://wakamsha.github.com/dev.cm/jhc-study/08/shake3.html" target="_blank" rel="noopener noreferrer">View demo (shake3)</a></li> </ul> <h2 id="toc-jquerystatic">jQueryプラグインに静的(static)関数を追加する</h2> <p>jQueryプラグインには静的関数を追加する事が出来ます。これによってプラグインに<span class="bold">ショートカットメソッド</span>や更なる機能の拡張などといったことが可能となり、より柔軟性を高める事が期待できます。</p> <p class="code-summary bold">jquery.shake4.js</p> ;(function($) { $.fn.shake = function(options) { // jquery.shake3.jsと同じ処理・・・ }; // 内部用関数 - ガクブル実行 function doshake($obj, opts) { // jquery.shake3.jsと同じ処理・・・ }; // 追加するためのベースを定義 $.shake = {}; // 静的関数 1 $.shake.yurayura = function($obj) { var opts = { speed: 1000, shakes: 10, x: 20 }; doshake($obj, opts); }; // 静的関数 2 $.shake.gakuburu = function($obj) { var opts = { speed: 25, shakes: 100, x: 10 }; doshake($obj, opts); }; // shakeプラグインのデフォルトオプション $.fn.shake.defaults = { // jquery.shake3.jsと同じ処理・・・ }; }) (jQuery);
jQueryプラグインの静的関数を呼び出すには、以下のようにします。
$.shake.yurayura($('div')); $.shake.gakuburu($('div'));
イベント駆動型のjQueryプラグイン
jQueryプラグインの動作を制御する方法として、イベントを利用するというのがあります。プラグインが呼び出されて要素に関数をバインドさせ、それぞれのアクションを必要なときに実行させたいとします。この場合のトリガーとしてイベントを発生させて、それで実行させることが出来れば期待通りの動きが実現できそうです。
そんな訳で、シンプルなスライドショーのプラグインを作ってみます。以下はその要件です。
- プラグインはimg要素を受け取り、画像のURL配列をオプションとして受け取る
- スライドショーには戻るボタンと進むボタンがあり、これらをクリックすることで表示する画像を切り替える事が出来る
- 画像の表示は自動的に切り替わり、循環して表示される
手始めに基本的な枠組みを作ります。
jquery.slideshow.js
(function($) { $.fn.slideshow = function(options) { // 要素を退避 var elements = this; // 渡されたオプションとデフォルトをマージ var opts = $.extend({}, $.fn.slideshow.defaults, options); // 要素をひとつずつ処理 elements.each(function() { var $img = $(this); var current = 0; // ここに必要なロジックを記述・・・ }); return this; }; // デフォルトオプション $.fn.slideshow.defaults = { // ここにデフォルト値を記述・・・ }; }) (jQuery);
渡したURLの画像を表示させ、さらに戻るボタンと進むボタンで表示される画像を切り替える処理を加えていきます。
(function($) { $.fn.slideshow = function(options) { // 要素を退避 var elements = this; // 渡されたオプションとデフォルトをマージ var opts = $.extend({}, $.fn.slideshow.defaults, options); // 要素をひとつずつ処理 elements.each(function() { var $img = $(this); var current = 0; function show(index) { var total = opts.images.length; while (index < 0) { index += total; } while (index >= total) { index -= total; } current = index; $img.attr('src', opts.images[index]); } function prev() { show(current - 1); } function next() { show(current + 1); } $img.bind('prev', prev) .bind('next', next) .bind('goto', function(event, index) { show(index); } ); }); return this; }; // デフォルトオプション $.fn.slideshow.defaults = { // ここにデフォルト値を記述・・・ }; }) (jQuery);
デフォルトオプションはひとまず置いておくとして、これでボタンクリックで画像を切り替える処理の実装は出来ました。プラグインの呼び出し側は次のように実装します。
eventdriven.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Slideshow sample</title> <link rel="stylesheet" href="css/eventdriven.css" /> <script src="http://code.jquery.com/jquery-1.8.2.min.js"></script> <script src="js/jquery.slideshow.js"></script> <script> $(function() { var $image = $('#slideshow'); $image.slideshow({ images: ['img/jhc_less1.png', 'img/jhc_selector.png', 'img/parallax.png'] }); $('#prev').click(function(event) { event.preventDefault(); $image.trigger('prev'); }); $('#next').click(function(event) { event.preventDefault(); $image.trigger('next'); }); $image.trigger('goto', 0); }); </script> </head> <body> <div> <h1>Event-driven Plugin</h1> <p>jQuery samples</p> <ul> <li><a id="prev" href="#">戻る</a></li> <li><img id="slideshow" /></li> <li><a id="next" href="#">進む</a></li> </ul> </div> </body> </html>
ここまでのコードをWebブラウザ上で確認してみます。配列で渡したURLがプラグイン側に読み込まれ、最初の画像が表示されたかと思います。画像は進むボタン戻るボタンで切り替えることが出来ます。この二つのボタンは、prevイベントとnextイベントを発火させているだけで、切り替え処理はプラグインがそれらのイベントを受け取って内部で行っています。
次に画像が自動的に切り替わる処理を実装します。以下のプラグインに以下のコードを追加します。
// 要素をひとつずつ処理 elements.each(function() { var $img = $(this); var current = 0; function show(index) { var total = opts.images.length; while (index < 0) { index += total; } while (index >= total) { index -= total; } current = index; $img.attr('src', opts.images[index]); if (auto) { start(); } } ・ ・ ・ var auto = false; var id; function start() { stop(); auto = true; id = setTimeout(next, opts.interval); } function stop() { auto = false; clearTimeout(id); } $img.bind('start', start).bind('stop', stop); });
デフォルトオプションにもコードを追加します。
// デフォルトオプション $.fn.slideshow.defaults = { images: [], interval: 2000 };
プラグインの呼び出し側(HTML)には以下のコードを追加します。
$image.trigger('start');
おわりに
既存のjQueryプラグインを探したけど、自分がホントに欲しい機能とは微妙に違っているものしか見つからないといったシチュエーションはよくあります。またjQueryは、prototype拡張という考え方がほとんど無いかわりに、足りない機能はプラグインを作ってそれで補完する$.fnを使って自作関数をjQueryに組み込む事で、prototype拡張を実現するというポリシーで成り立っています。世の中にはとんでもないような高機能のプラグインがドッサリとありますが、基本的な骨格はこれまでに挙げたサンプルコードとそこまでの違いはありません。足りない機能、欲しい機能はプラグインを自作するという習慣を少しずつ積み重ねていけば、やがては大規模で高性能なプラグインを作れるだけのスキルが身についてきます。
はじめは凄く小さなものから少しずつ。